KURZUS: Programozás alapjai

MODUL: III. modul

7. lecke: Előfeldolgozó (preprocessor)

| Már eddig is jó hasznát vettük az előfeldolgozó #define és #include direktíváinak, de ennél valójában sokkal többre is képes. A teljesség igénye nélkül végignézzük az előfeldolgozó legfontosabb képességeit, megvizsgáljuk a direktívák alkalmazásának szabályait és az általuk nyújtott, érdekesebb lehetőségeket. Ezek közül az egyik a paraméteres #define, vagy más néven függvényszerű makrók készítésének lehetősége. Ezek ugyanúgy használhatóak egy programban, mint a függvények, de ha a kód egyszerű, ám sok hívásra kerül sor, akkor hatékonyabb megoldást kínálnak a függvényekhez képest.
A lecke végére a hallgatónak tisztában kell lennie
Egyszerű és paraméteres makrók létrehozásának módjával. Fejfájlok többszöri bekapcsolásának elkerülésére feltételes fordítással. Ismernie kell a legfontosabb karakter osztályozó és konverziós makrókat. A makrókifejtés szabályaival, módjával.
A lecke kulcsfogalmai: egyszerű és paraméteres makró, makródefiníció, makróazonosító, makrótest, makróhelyettesítés, feltételes fordítás.
A direktívák használatának általános szabályai
A fordító első menete során mindig meghívja az előfeldolgozót a forrásfájlra.
Ha szeretnénk tudni, hogy fest az előfeldolgozáson átesett forrásfájl (a fordítási egység), akkor a programfejlesztő rendszerben utána kell nézni, hogy tehetjük láthatóvá az előfeldolgozás eredményét. Az előfeldolgozott forrásfájlban aztán megtekinthetjük:
a makrók kifejtését, a behozott (include) fájlokat, a feltételes fordítást, a szomszédos karakterlánc konstansok egyesítését, a direktívák elhagyását és a megjegyzések kimaradását (helyettesítését egyetlen szóköz karakterrel).
A nem karakter, vagy karakterlánc konstansban előforduló, egymást követő, több fehér karaktert mindig eggyel helyettesíti az előfeldolgozó.
Az előfeldolgozó direktívák írásszabálya, mely független a C nyelv többi részétől, a következő:
A sor első, nem fehér karakterének #-nek kell lennie. A #-et követheti aztán fehér karakter a soremelést kivéve. A sorokra tördelés nagyon lényeges elem, mert az előfeldolgozó sorokra bontva elemzi a forrásszöveget. A karakter konstansban, a karakterlánc konstansban és a megjegyzésben levő # karakter nem minősül előfeldolgozó direktíva kezdetének. A direktívákat - miután nem C utasítások - tilos pontosvesszővel lezárni! Ha a direktívában a soremelést \ karakter előzi meg, akkor a következő sor folytatássornak minősül, azaz az előfeldolgozó elhagyja a \-t és a soremelést, s egyesíti a két sort. Az előfeldolgozó direktívákba megjegyzés is írható. Az előfeldolgozó direktívák bárhol elhelyezkedhetnek a forrásfájlban, de csak a forrásfájl ezt követő részére hatnak, egészen a fájl végéig.
A teljes szintaktika az előfeldolgozó direktívákra a feltételes fordítástól eltekintve úgy, hogy a már megismert fogalmakat újra nem definiáljuk, a következő:
csoport: csoport-rész csoport csoport-rész
csoport-rész: előfeldolgozó-szimbólumok újsor feltételes-fordítás vezérlő-sor
előfeldolgozó-szimbólumok: előfeldolgozó-szimbólum előfeldolgozó-szimbólumok előfeldolgozó-szimbólum
előfeldolgozó-szimbólum: <fájlazonosító> (csak #include direktívában) "fájlazonosító" (csak #include direktívában) azonosító (nincs kulcsszó megkülönböztetés) konstans karakterlánc-konstans operátor elválasztó-jel bármilyen nem fehér karakter, mely az előzőek egyike sem
vezérlő-sor: #include előfeldolgozó-szimbólumok újsor #define azonosító <előfeldolgozó-szimbólumok> újsor #define azonosító(azonosítólista) előfeldolgozó-szimbólumok újsor #undef azonosító újsor #line előfeldolgozó-szimbólumok újsor #error <előfeldolgozó-szimbólumok> újsor #pragma <előfeldolgozó-szimbólumok> újsor # újsor
újsor: soremelés
Az előfeldolgozó-szimbólum definíciójában a fájlazonosító körüli <> a szintaktika része, és nem az elhagyhatóságot jelöli, mint máskor. Látszik, hogy a feldolgozásban bármely karakter, ami nem foglalt az előfeldolgozónak, szintén szintaktikai egységet képez.
Üres (null) direktíva
A csak egy # karaktert tartalmazó sor. Ezt a direktívát elhagyja az előfeldolgozó, azaz hatására nem történik semmi.
#include direktíva
Az #include direktíva lehetséges alakjait:
#include <fájlazonosító> újsor #include "fájlazonosító" újsor
már az Alapismeretek 1/2 leckében részleteztük. Tudjuk, hogy a vezérlő-sort az előfeldolgozó a megadott fájl teljes tartalmával helyettesíti. Magát a fájlt "fájlazonosító" esetén előbb az aktuális könyvtárban (abban, ahonnét azt a fájlt töltötte, amelyikben ez az #include direktíva volt), majd és <fájlazonosító>-s esetben a programfejlesztő rendszerben előírt utakon keresi.
A <> és az idézőjelek között nincs makróhelyettesítés. Ha a fájlazonosító-ban >, ", ', \, vagy /* karakterek vannak, az előfeldolgozás eredménye meghatározatlan. Ha macskakörmös esetben a fájlazonosító elérési utat is tartalmaz, akkor a fájlt a preprocesszor csak abban a könyvtárban keresi, és sehol másutt.
A direktíva
#include előfeldolgozó-szimbólum újsor
formáját előbb feldolgozza az előfeldolgozó, de a helyettesítésnek itt is <>, vagy "" alakot kell eredményeznie, s a hatás ennek megfelelő. Például:
    #define ENYIM "\Cfajlok\Munka16\Pipo.h"
    /* . . . */
    #include ENYIM
Az #include direktívák egymásba ágyazhatók, azaz a behozott fájl újabb #include-okat tartalmazhat, s azok megint újabbakat, és így tovább. Az egymásba ágyazgatásokkal azonban vigyázni kell, mert egyes programfejlesztő rendszerek ezek szintjét korlátozhatják!
Egyszerű #define makró
A #define direktíva makrót definiál (makródefiníció). A makró szimbólumhelyettesítő mechanizmus függvényszerű formális paraméterlistával vagy anélkül.
Előbb a paraméter nélküli esettel foglalkozunk! Ilyenkor a direktíva alakja:
#define azonosító <előfeldolgozó-szimbólumok> újsor
Hatására az előfeldolgozó a forráskódban ez után következő minden makróazonosító előfordulást helyettesít a lehet, hogy üres előfeldolgozó-szimbólumokkal. Ha üres előfeldolgozó-szimbólumokkal történik a helyettesítés, a makróazonosító akkor is definiált, azaz #if defined vagy #ifdef direktívával "rákérdezhetünk" az azonosítóra, de a makróazonosító minden forrásszövegbeli előfordulásának eltávolítását jelenti tulajdonképp. Nem történik meg a helyettesítés, ha a makróazonosító karakter vagy karakterlánc konstansban, vagy megjegyzésben, vagy más makróazonosító részeként található meg. Ezt a folyamatot makrókifejtésnek (expansion) nevezik. Az előfeldolgozó-szimbólumokat szokás makrótest névvel is illetni. Például:
    #define HI "Jó napot!"
    #define begin {
    #define end }
    #define then
    #define NIL ""
    #define EGY 1
    int main(void)
      begin /* Helyettesítés {-re. */
      int i=8, k=i+EGY;/* Csere k=i+1-re.*/
      puts(HI); /* puts("Jó napot!"); lesz belőle */
      puts(NIL); /* puts(""); lesz a sorból. */
      puts("then"); /* Nincs helyettesítés, mert a makróazonosító
                       karakterlánc konstansban van. */
      if(++i<k)
      /* A sor eleji then semmire helyettesítése, de a */
        then puts("Ez a then-ág!\n"); /* másik marad.*/
      else puts("Ez az else-ág!\n");
      return 0;
    end /* Csere }-re. */
A makrókifejtés utáni makróazonosítókat is helyettesíti a preprocesszor, azaz a makrók is egymásba ágyazhatók.
A makróazonosító újradefiniálása csak akkor nem hiba, ha az előfeldolgozó-szimbólumok tökéletesen, pozíció-helyesen azonosak. Ettől eltérő újradefiniálás csak a rávonatkozó #undef direktíva után lehetséges.
A nyelv kulcsszavait is alkalmazhatjuk makródefiníciókban, legfeljebb kissé értelmetlennek tekinthető az eljárásunk:
    #define LACI for
    #define PAPI while
    #define int long
de a következő fejezetben ismertetett, szabványos, előredefiniált makrók nem jelenhetnek meg sem #define, sem #undef direktívákban.
Előredefiniált makrók
Néhány makró előredefiniált az előfeldolgozó rendszerben, s kifejtetésükkel speciális információ képezhető. Ezek a makrók mind defined típusúak. Nem tehetők definiálatlanná, és nem definiálhatók újra. A szabványos, előredefiniált makrók és jelentésük a következő:
__DATE__ | HHH NN ÉÉÉÉ alakú karakterlánc, s az aktuális forrásfájl előfeldolgozása kezdetének dátuma. A HHH hárombetűs hónapnév rövidítés (Jan, Feb stb.). Az NN 1 és 31 közötti napszám, s így tovább.
__FILE__ | Karakterláncként a feldolgozás alatt álló forrásfájl azonosítóját tartalmazza. A makró változik #include és #line direktíva hatására, valamint ha a forrásfájl fordítása befejeződik.
__LINE__ | Decimális értékként a feldolgozás alatti forrásfájl aktuális sorának sorszáma. A sorszámozás 1-től indul. Módosíthatja például a #line direktíva is.
__STDC__ | Definiált és 1, ha ANSI kompatibilis fordítás van, máskülönben definiálatlan.
__TIME__ | OO:PP:MM alakú karakterlánc, s a forrásfájl feldolgozása megkezdésének idejét tartalmazza.
#undef direktíva
#undef azonosító újsor
A direktíva definiálatlanná teszi a makróazonosítót, azaz a továbbiakban nem vesz részt a makrókifejtésben.
Azt, hogy egy makróazonosító definiált-e vagy sem a forráskódban, megtudhatjuk a
#ifdef azonosító #ifndef azonosító
direktívák segítségével, azaz a makródefiníciónál ajánlható a következő stratégia:
    #ifndef MAKROKA
      #define MAKROKA 162
    #endif
Az ismeretlen makróazonosítóra kiadott #undef direktívát nem tekinti hibának az előfeldolgozó.
A definiálatlanná tett makróazonosító később újradefiniálható akár más előfeldolgozó-szimbólumokkal. A #define-nal definiált és közben #undef-fel definiálatlanná nem tett makróazonosító definiált marad a forrásfájl végéig. Például:
    #define BLOKK_MERET 512
    /* . . . */
    puff = BLOKK_MERET*blkszam; /* Kifejtés: 512*blkszam. */
    /* . . . */
    #undef BLOKK_MERET
    /* Innét a BLOKK_MERET ismeretlen makróazonosító. */
    /* . . . */
    #define BLOKK_MERET 128
    /* . . . */
    puff = BLOKK_MERET*blkszam; /* Kifejtés: 128*blkszam. */
Paraméteres #define direktíva
A direktíva alakja ilyenkor:
#define azonosító(azonosítólista) előfeldolgozó-szimbólumok újsor
Az azonosítólista egymástól vesszővel elválasztott formális paraméterazonosítókból áll. A makrót hívó aktuális paraméterlistában ugyanannyi paraméternek kell lennie, mint amennyi a formális paraméterlistában volt, mert különben hibaüzenetet kapunk.
Ugyan a makróra is a függvénnyel kapcsolatos fogalmakat használjuk képszerűségük végett, de ki kell hangsúlyozni, hogy a makró nem függvény! A makróazonosító és a paraméterlistát nyitó kerek zárójel közé pl. semmilyen karakter sem írható, hisz rögtön egyszerű #define-ná tenné a paraméteres direktívát!
Az előfeldolgozó előbb a makróazonosítót helyettesíti, s csak aztán a zárójelbe tett paramétereket:
    Definíció: #define KOB(x) ((x)*(x)*(x))
    Forrássor: n = KOB(y);
    Kifejtve:  n = ((y)*(y)*(y));
A látszólag redundáns zárójeleknek nagyon fontos szerepük van:
    Definíció: #define KOB(x) (x*x*x)
    Forrássor: n = KOB(y + 1);
    Kifejtve:  n = (y + 1*y + 1*y + 1);
A külső zárójelpár a kifejezésekben való felhasználhatóságot biztosítja:
    Definíció: #define SUM(a,b) (a)+(b)
    Forrássor: n = 14.5 * SUM(x*y, z-8);
    Kifejtve:  n = 14.5 * (x*y)+(z-8);
A zárójelbe, vagy aposztrófok, idézőjelek közé tett vesszők nem minősülnek a listában azonosító elválasztónak:
    Definíció: #define SUM(a,b) ((a)+(b))
    Forrássor: return SUM(f(i,j), g(k,l));
    Kifejtve:  return ((f(i,j))+(g(k,l)));
    Definíció: #define HIBA(x,lanc) hibaki("Hiba: ",x,lanc)
    Forrássor: HIBA(2,"Üssön Enter-t, s Esc-t!");
    Kifejtve:  hibaki("Hiba: ",2,"Üssön Enter-t, s Esc-t!");
Folytatássor most is sorvégi \ jellel képezhető:
    Definíció: #define FIGYU "Ez igazából egyetlen \
    sornak minősül!"
    Forrássor: puts(FIGYU);
    Kifejtve: puts("Ez igazából egyetlen sornak minősül!");
A makró nem függvény, tehát semmilyen ellenőrzés sincs a paraméterek adattípusára! Ha az aktuális paraméter kifejezés, akkor kiértékelése többször is megtörténik:
    int kob(int x){ return x*x*x;}
    #define KOB(x) ((x)*(x)*(x))
    /* . . . */
    int b = 0, a = 3;
    b = kob(a++); /* b == 27 és a == 4. */
    a = 3;
    b = KOB(a++); /* Kifejtve: ((a++)*(a++)*(a++)),
    azaz b == 60 és a == 6. */
Karaktervizsgáló függvények (makrók)
Megismerkedtünk az előző fejezetekben a makrók előnyös, és persze hátrányos tulajdonságaival. Mindezek dacára a makrók használata a C-ben elég széleskörű. (Nézzük csak meg a szabványos stdio.h fejfájlt, s látni fogjuk, hogy a szabvány bemenetet és kimenetet kezelő getchar és putchar rutinok makrók!)
A szabványos ctype.h fejfájlban deklarált függvények karakter osztályozást végeznek. A rutinok c paramétere int ugyan, de az értékének unsigned char típusban ábrázolhatónak, vagy EOF-nak kell lennie. (Egyes fordítók olyan futtatható állományt generálhatnak, melyek diagnosztikai üzenettel (assertion) leállítják a futást, ha ez a feltétel nem teljesül. Ékezetes betűk kódjai nagy valószínűséggel kívül fognak esni az engedélyezett intervallumon.) A visszatérési értékük ugyancsak int logikai jelleggel, azaz nem zérus (igaz), ha a feltett kérdésre adott válasz igen, ill. zérus (hamis), ha nem.
A teljesség igénye nélkül felsorolunk néhány karakterosztályozó függvényt!
Függvény | Kérdés
islower(c) | c kisbetű-e?
isupper(c) | c nagybetű-e?
isalpha(c) | islower(c) | isupper(c)
isdigit(c) | c decimális számjegy-e?
isalnum(c) | isalpha(c) | isdigit(c)
isxdigit(c) | c hexadecimális számjegy-e?
isspace(c) | c fehér karakter-e? (szóköz, soremelés, lapemelés, kocsi vissza, függőleges vagy vízszintes tabulátor)
isprint(c) | c nyomtatható karakter-e?
Meg kell még említeni két konverziós rutint is:
int tolower(c); int toupper(c);
melyek c értékét kisbetűvé (tolower), ill. nagybetűvé (toupper) alakítva szolgáltatják, ha c betű karakter, de változatlanul adják vissza, ha nem az.
Írjuk át pelda17.c- t úgy, hogy karaktervizsgáló függvényeket használjon!
    /* PELDA19.C: A bemenet karaktereinek leszamlalasa kategoriankent
                  az is... fuggvenyek segitsegevel. */
    #include <stdio.h>
    #include <ctype.h>
    void main(void){
      short k, num=0, feher=0, egyeb=0;
      printf("Bemeneti karakterek leszamlalasa\n"
             "kategoriankent EOF-ig, vagy Ctrl+Z-ig.\n");
      while((k=getchar())!=EOF)
        if(isdigit(k)) ++num;
        else if (isspace(k)) ++feher;
        else ++egyeb;
      printf("Karakter szamok:\n----------------\n"
             "numerikus: %5hd\nfeher:     %5hd\n"
             "egyeb:     %5hd\n----------------\n"
             "ossz: %10ld\n", num, feher, egyeb,
             (long)num+feher+egyeb); }
Írjuk még át ugyanebben a szellemben a pelda18.c egesze függvényét is!
    #include <ctype.h>
    #define HSZ sizeof(int)/sizeof(short)*5
    int egesze(char s[]){
      int i = 0, kezd;
      while(isspace(s[i])) ++i;
      if(s[i]=='+' || s[i]=='-') ++i;
      kezd=i; /* A számjegyek itt kezdődnek. */
      while(isdigit(s[i]) && i-kezd<HSZ) ++i;
      if(kezd==i || !isspace(s[i]) && s[i]!=0) return 0;
      else return 1; }
A pelda13.c-beli strup rutin így módosulna:
    #include <ctype.h>
    void strup(char s[]){
      int i;
      for(i=0; s[i]; ++i) s[i]=toupper(s[i]); }
Milyen előnyei vannak a karakterosztályozó függvények használatának?
A kód rövidebb, és ez által gyorsabb is. A program portábilis lesz, hisz függetlenedik az ASCII (vagy más) kódtábla sajátosságaitól.
Az olvasó utolsó logikus kérdése már csak az lehet, hogy miért pont a makrók között tárgyaljuk a karaktervizsgáló rutinokat? Több C implementáció makróként valósítja meg ezeket a függvényeket. Erre mutatunk itt be egy szintén nem teljes körű példát azzal a feltétellel, hogy a CHAR_BIT (bitek száma a char típusban - lásd limits.h- t!) makró értéke 8.
    /* Bitmaszk értékek a lehetséges karaktertípusokra: */
    #define _UPPER 0x1 /* Nagybetű. */
    #define _LOWER 0x2 /* Kisbetű. */
    #define _DIGIT 0x4 /* Decimális számjegy. */
    #define _SPACE 0x8 /* '\t','\r','\n','\v','\f' */
    #define _PUNCT 0x10 /* Elválasztó-jel. */
    #define _CONTROL 0x20 /* Vezérlő karakter. */
    #define _BLANK 0x40 /* Szóköz. */
    #define _HEX 0x80 /* Hexadecimális számjegy. */
    /* Globális tömb, melyben a rendszer mindenegyes kódtábla
       pozícióra beállította ezeket a biteket: */
    extern unsigned char _ctype[];
    /* Néhány makró: */
    #define islower(_c) (_ctype[_c]&_LOWER)
    #define isupper(_c) (_ctype[_c]&_UPPER)
    #define isalpha(_c) (_ctype[_c]&(_UPPER|_LOWER))
    #define isdigit(_c) (_ctype[_c]&_DIGIT)
    #define isalnum(_c) (_ctype[_c]&(_UPPER|_LOWER|_DIGIT))
    #define isxdigit(_c) (_ctype[_c]&_HEX)
    #define isspace(_c) (_ctype[_c]&(_SPACE|_BLANK))
    #define isprint(_c) (_ctype[_c]&(_BLANK|_PUNCT|_UPPER|_LOWER|_DIGIT))
Ha az olvasóban felmerült volna az a gondolat, hogy mi van akkor, ha ugyanolyan nevű makró és függvény is létezik, akkor arra szeretnénk emlékeztetni, hogy:
Az előfeldolgozás mindig a fordítás előtt történik meg, s így mindenből makró lesz. Ha #undef direktívával definiálatlanná tesszük a makrót, akkor attól kezdve csak függvény lesz a forrásszövegben. Ha a hívásban redundáns zárójelbe zárjuk a makró vagy a függvény nevét, akkor az előfeldolgozó ezt nem fejti ki, tehát bizonyosan függvényhívás lesz belőle. ...(makrónév)(paraméterek)...
Feltételes fordítás
feltételes-fordítás: if-csoport <elif-csoportok> <else-csoport> endif-sor
if-csoport: #if konstans-kifejezés újsor <csoport> #ifdef azonosító újsor <csoport> #ifndef azonosító újsor <csoport>
elif-csoportok: elif-csoport elif-csoportok elif-csoport
elif-csoport: #elif konstans-kifejezés újsor <csoport>
else-csoport: #else újsor <csoport>
endif-sor: #endif újsor
újsor: soremelés
A feltételes direktívák szerint kihagyandó forrássorokat az előfeldolgozó törli a forrásszövegből, s a feltételes direktívák sorai maguk pedig kimaradnak az eredmény fordítási egységből. A feltételes direktívák által képzett konstrukciót - melyet rögtön bemutatunk egy általános példán - mindenképpen be kell fejezni abban a forrásfájlban, amelyben elkezdték.
#if konstans-kifejezés1 <szekció1> <#elif konstans-kifejezés2 <szekció2>> /* . . . */ <#elif konstans-kifejezésN <szekcióN>> <#else <végső-szekció>> #endif
Lássuk a kiértékelést!
1. | Ha a konstans-kifejezés1 értéke nem zérus (igaz), akkor a preprocesszor a szekció1 sorait feldolgozza, és az eredményt átadja a fordítónak. A szekció1 természetesen üres is lehet. Ezután az ezen #if-hez tartozó összes többi sort a vonatkozó #endif-fel bezárólag kihagyja, s az #endif-et követő sorral folytatja a munkát az előfeldolgozó.
2. | Ha a konstans-kifejezés1 értéke zérus (hamis), akkor a preprocesszor a szekció1-et teljes egészében elhagyja. Tehát nincs makrókifejtés, és nem adja át a feldolgozott darabot a fordítónak! Ezután viszont a következő #elif konstans-kifejezése kiértékelésébe fog, s így tovább.
3. | Összesítve az #if-en és az #elif-eken lefelé haladva az a szekció kerül előfeldolgozásra, s ennek eredménye fordításra, melynek konstans-kifejezése igaznak bizonyul. Ha egyik ilyen konstans-kifejezés sem igaz, akkor az #else végső-szekciójára vonatkoznak az előbbiekben mondottak.
Az #if . . . #endif konstrukciók tetszőleges mélységben egymásba ágyazhatók.
Az #if . . . #endif szerkezetbeli konstans-kifejezéseknek korlátozott, egész típusúaknak kell lenniük! Konkrétabban egész konstanst, karakter állandót tartalmazhat a kifejezés, és benne lehet a defined operátor is. Tilos használni viszont benne explicit típuskonverziót, sizeof kifejezést, enumerátort és lebegőpontos konstanst, mint normál egész típusú konstans kifejezésekben! Az előfeldolgozó közönséges makróhelyettesítési menettel dolgozza fel a konstans-kifejezéseket.
A feltételes fordítás jó szolgálatot tehet, ha olyan programrészeket kell készíteni, melyek portábilis módon nem valósíthatóak meg, csak pl. operációs rendszerenként eltérő módon. Ekkor minden támogatott rendszer kódját belefoglalják a forrásszövegekbe, de a ténylegesen a programba belefordítandó részt egy makró létezésével, vagy megfelelő értékűre állításával választják ki.
A defined operátor
Makróazonosítók definiáltságának ellenőrzésére való, s csak #if és #elif konstans-kifejezéseiben szerepelhet. A
defined(azonosító)
vagy a
defined azonosító
alak a makróazonosító definiáltságára kérdez rá. Miután a válasz logikai érték a defined szerkezetek logikai műveletekkel is kombinálhatók a konstans-kifejezésekben. Például:
    #if defined(makro1) && !defined(makro2)
Ha biztosítani szeretnénk azt, hogy a fordítási egységbe egy bizonyos fejfájl (legyen header.h) csak egyszer épüljön be, akkor a fejfájl szövegét következőképp kell direktívákba foglalni:
    #if !defined(_HEADERH)
      #define _HEADERH
      /* Itt van a fejfájl szövege. */
    #endif
Ilyenkor akárhány #include is jön a forrásfájlban a header.h fejfájlra, a behozatala csak először történik meg, mert a további bekapcsolásokat a _HEADERH makró definiáltsága megakadályozza.
Az #ifdef és az #ifndef direktívák
Az #ifdef direktíva egy makróazonosító definiáltságára, s az #ifndef viszont a definiálatlanságára kérdez rá, azaz:
#ifdef azonosító ? #if defined(azonosító) #ifndef azonosító ? #if !defined(azonosító)
#line sorvezérlő direktíva
#line egész-konstans <"fájlazonosító"> újsor
Jelzi az előfeldolgozónak, hogy a következő forrássor egész-konstans sorszámú, és a fájlazonosító nevű fájlból származik. Miután az aktuálisan feldolgozás alatt álló forrásfájlnak is van azonosítója a fájlazonosító paraméter elhagyásakor a #line az aktuális fájlra vonatkozik.
A makrókifejtés a #line paramétereiben is megtörténik.
Vegyünk egy példát!
    /* PELDA.C: a #line direktívára: */
    #include <stdio.h>
    #line 4 "PIPI.C"
    void main(void) {
      printf("\nA(z) %s fájl %d sorában vagyunk!", __FILE__,
             __LINE__);
      #line 12 "PELDA.C"
      printf("\n");
      printf("A(z) %s fájl %d sorában vagyunk!", __FILE__,
             __LINE__);
      #line 8
      printf("\n");
      printf("A(z) %s fájl %d sorában vagyunk!\n", __FILE__,
             __LINE__); }
Az előállított standard kimenet a következő lehet:
    #line 1 "pelda.c"
    #line 1 "c:\\msdev\\include\\stdio.h"
    . . .
    #line 524 "c:\\msdev\\include\\stdio.h"
    #line 3 "pelda.c"
    #line 4 "PIPI.C"
    void main(void) {
      printf("\nA(z) %s fájl %d sorában vagyunk!", "PIPI.C",
             6);
      #line 12 "PELDA.C"
      printf("\n");
      printf("A(z) %s fájl %d sorában vagyunk!", "PELDA.C",
             13);
      #line 8 "PELDA.C"
      printf("\n");
      printf("A(z) %s fájl %d sorában vagyunk!\n","PELDA.C",
             9); }
A #line direktíva tulajdonképpen a __FILE__ és a __LINE__ előredefiniált makrók értékét állítja. Ezek a makróértékek a fordító hibaüzeneteiben jelennek meg. Szóval a direktíva diagnosztikai célokat szolgál.
#error direktíva
#error <hibaüzenet> újsor
direktíva üzenetet generál, és befejeződik a fordítás. Az üzenet alakja lehet a következő:
Error: fájlazonosító sorszám: Error directive: hibaüzenet
Rendszerint #if direktívában használatos. Például:
    #if (SAJAT!=0 && SAJAT!=1)
      #error A SAJAT 0-nak vagy 1-nek definiálandó!
    #endif
#pragma direktívák
#pragma <előfeldolgozó-szimbólumok> újsor
A direktívák gép és operációs rendszerfüggők. Bennük a #pragma kulcsszót követő szimbólumok mindig objektumai a makrókifejtésnek, és tulajdonképpen speciális fordítói utasítások, s ezek paraméterei. Az előfeldolgozó a fel nem ismert #pragma direktívát figyelmen kívül hagyja.

| Feladatok
| 1. Létre szerettünk volna hozni egy olyan void atalakit(char forras[], char cel[]) függvényt, ami a forras-ban adott mondat szavaiból Camel Case formázású azonosítót állít elő, és ezt elhelyezni a cel-ban. Az eredmény lánc az eredetiből úgy keletkezik, hogy a fehér karaktereket elhagyják, minden szó első betűjét nagyra, a többit kicsire cserélik, kivéve a legelső szót, melynek kezdőbetűje is kicsi. Pl. "Nagyon hosszu mondat" -> "nagyonHosszuMondat".
    /*01*/#include <ctype.h>
    /*02*/void atalakit(char forras[], char cel[]) {
    /*03*/  int i, j, feher = 1;
    /*04*/  for(i=0; isspace(forras[i]); i++);
    /*05*/  for(; forras[i]; i++) {
    /*06*/    if(!isspace(forras[i])) {
    /*07*/      if(feher) cel[j] = toupper(forras[i]);
    /*08*/      else cel[j] = tolower(forras[i]);
    /*09*/      j++;
    /*10*/      feher = 0;
    /*11*/    } else {
    /*12*/      feher = 1;
    /*13*/    }
    /*14*/  }
    /*15*/  cel[j] = 0;
    /*16*/}
| Jelölje meg, mely sorok felhasználásával lehetne működőképessé tenni hibás függvényünket!
| /*01*/#include <stdio.h>
| /*03*/ int i, j, feher = 0;
| /*03*/ int i, j = 0, feher = 0;
| /*03*/ int i, j = 1, feher = 1;
| /*05*/ for(i=0; forras[i]; i++) {
| /*15*/ cel[j] = 1;
| 2. Olyan függvényt szerettünk volna létrehozni, amely eldönti a paraméterként kapott karakterláncról, hogy formailag Neptun kódnak tekinthető-e, és logikai értékkel válaszol. A Neptun kódtól elvárjuk, hogy pontosan hat jelből álljon, csak nagybetűket és számjegyeket tartalmazzon. Sajnos nem jártunk sikerrel.
    /*1*/#include <ctype.h>
    /*2*/int neptune(char s[]) {
    /*3*/  int i;
    /*4*/  for(i=0; i<6; i++)
    /*5*/    if(!isdigit(s[i]) && !isalpha(s[i])) return 0;
    /*6*/  if(s[i]) return 0; else return 1; }
| Jelölje meg azokat a sorokat, amellyel a függvény működőképessé tehető!
| /*5*/ if(!isdigit(s[i]) || !isalpha(s[i])) return 0;
| /*5*/ if(isdigit(s[i]) && isalpha(s[i])) return 0;
| /*5*/ if(!isdigit(s[i]) && !isupper(s[i])) return 0;
| /*5*/ if(!isalnum(s[i])) return 0;
| /*6*/ if(!s[i]) return 0; else return 1; }
| /*6*/ if(s[i+1]) return 0; else return 1; }
| 3. Készítsen olyan függvényt, amely megállapítja a paraméterként átadott karakterláncról, hogy érvényes nevet tartalmaz-e! A névtől azt várjuk, hogy 2 vagy 3 szóból (vezeték és keresztnevek) álljon, a szavak nagybetűkkel kezdődjenek, majd kisbetűkkel folytatódjanak, egymás mellett ne álljon több szóköz, és ne is kezdődjön, ne is fejeződjön be a név szóközzel!
    #include <ctype.h>
    int neve(char s[]) {
      int i, db;
      if(!isupper(s[0])) return 0; /* Nagybetuvel kezdodjon */
      for(i=1, db=0; s[i]; i++) {
        if(s[i]==' ') db++; /* Szamoljuk a szokozoket */
        if(s[i]==' ' && s[i-1]==' ') return 0; /* Nem lehet ket szokoz egymas utan */
        if(isupper(s[i]) && s[i-1]!=' ') return 0; /* Szokoz utan nagybetu */
        if(islower(s[i]) && !isalpha(s[i-1])) return 0; /* Nagybetu elott betu */
      }
      if(s[i-1]==' ') return 0; /* Nem fejezodhet be szokozzel */
      if(db<1 || db>2) return 0; /* Tul sok vagy keves szo? */
      return 1; /* Minden OK */
    }
| 4. Készítsen olyan paraméteres makrót, ami a paraméter karakterláncról eldönti, hogy egy sakktábla mezőjét tartalmazza-e! A karakterlánc akkor formailag helyes, ha pontosan két karakterből áll, első karaktere az a és h közé eső (ezeket is beleértve) kis- vagy nagybetű, a második karaktere pedig az 1 és 8 közé eső számjegy karakter.
    #define sakke(s) (((s)[0]>='A'&&(s)[0]<='H'||(s)[0]>='a'&&(s)[0]<='h')\
                      &&((s)[1]>='1'&&(s)[1]<='8')&&!(s)[2])